package proxy;

import annotation.ApiServer;
import model.MethodInfo;
import model.ServerInfo;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;

public interface ProxyCreator {

    Object createProxy(Class<?> type);

    default ServerInfo extractServerInfo(Class<?> type) {
        ApiServer apiServer = type.getAnnotation(ApiServer.class);
        ServerInfo serverInfo = new ServerInfo(apiServer.value());
        return serverInfo;
    }

    default MethodInfo extractMethodInfo(Method method, Object[] args) {
        MethodInfo methodInfo = new MethodInfo();
        extractMethodAndUri(method, methodInfo);
        extractParamsAndBody(method, args, methodInfo);
        extractReturnInfo(method, methodInfo);
        return methodInfo;
    }

    private void extractParamsAndBody(Method method, Object[] args, MethodInfo methodInfo) {
        Map<String, Object> paramsMap = new LinkedHashMap<>();
        methodInfo.setParams(paramsMap);

        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);
            if (pathVariable != null) {
                paramsMap.put(pathVariable.value(), args[i]);
            }
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                paramsMap.put(pathVariable.value(), args[i]);
            }
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                methodInfo.setBody((Mono<?>) args[i]);
                methodInfo.setBodyElementType(extractElementType(parameters[i].getParameterizedType()));
            }
        }
    }

    private void extractMethodAndUri(Method method, MethodInfo methodInfo) {
        Annotation[] annotations = method.getAnnotations();
        for (Annotation an : annotations) {
            if (an instanceof GetMapping) {
                GetMapping getAn = (GetMapping) an;
                methodInfo.setMethod(HttpMethod.GET);
                methodInfo.setUri(getAn.value()[0]);
            } else if (an instanceof PostMapping) {
                PostMapping getAn = (PostMapping) an;
                methodInfo.setMethod(HttpMethod.POST);
                methodInfo.setUri(getAn.value()[0]);
            } else if (an instanceof DeleteMapping) {
                DeleteMapping getAn = (DeleteMapping) an;
                methodInfo.setMethod(HttpMethod.DELETE);
                methodInfo.setUri(getAn.value()[0]);
            } else if (an instanceof PutMapping) {
                PutMapping getAn = (PutMapping) an;
                methodInfo.setMethod(HttpMethod.PUT);
                methodInfo.setUri(getAn.value()[0]);
            }
        }
    }

    private void extractReturnInfo(Method method, MethodInfo methodInfo) {
        Class<?> returnType = method.getReturnType();
        methodInfo.setReturnFlux(returnType.isAssignableFrom(Flux.class));

        methodInfo.setReturnElementType(extractElementType(method.getGenericReturnType()));

    }

    private Class<?> extractElementType(Type genericReturnType) {
        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
        return (Class<?>) actualTypeArguments[0];
    }


}
